#include "fyp/shadows/VarPCF4.h"

//------------------------------------------------------------------------------
// additional header includes
#include "fyp/ObjectManager.h"
#include "fyp/ResourcePool.h"

#include "fyp/render/Renderer.h"

#include "fyp/Gui.h"
#include "DXUT.h"
#include "DXUTgui.h"

using namespace ming::fyp::render;

//------------------------------------------------------------------------------
namespace ming
{
namespace fyp
{
namespace shadows
{
	//--------------------------------------------------------------------------
	// TVarPCF4Gui
	//--------------------------------------------------------------------------
	class TVarPCF4Gui : public GuiImpl_t<TVarPCF4>
	{
	public:
		enum CONTROL_ID
		{
			ID_DISTANCE_FACTOR_TEXT,
			ID_DISTANCE_FACTOR,
			ID_REDUCED_SIZE,
		};

		//------------------------------
		// #cstor & #dstor
		TVarPCF4Gui(TVarPCF4 *model, TGuiManager *guiMgr) :
			GuiImpl_t<TVarPCF4>(model, guiMgr, "VarPCF4")
		{
			mDlg->AddStatic(ID_DISTANCE_FACTOR_TEXT, L"Distance Factor", 10, 180, 160, 20);
			mDlg->AddSlider(ID_DISTANCE_FACTOR, 10, 200, 200, 20, 0, 100, 50);

			mDlg->AddStatic(-1, L"Reduce Size", 10, 240, 100, 20);
			mDlg->AddComboBox(ID_REDUCED_SIZE, 120, 240, 120, 20);

			mDlg->GetComboBox(ID_REDUCED_SIZE)->AddItem(L"1/2", NULL);
			mDlg->GetComboBox(ID_REDUCED_SIZE)->AddItem(L"1/4", NULL);
			mDlg->GetComboBox(ID_REDUCED_SIZE)->AddItem(L"1/8", NULL);

			mDlg->GetComboBox(ID_REDUCED_SIZE)->SetSelectedByIndex(mModel->mCurrReduceFactor);

			UpdateDistanceFactor();
			UpdateReducedSize();
		}

		~TVarPCF4Gui() 
		{
		}
		
		//------------------------------
		// external operations
		virtual void OnControlEvent(int controlID, CDXUTControl *control)
		{
			
			if(controlID == ID_DISTANCE_FACTOR)
			{
				UpdateDistanceFactor();
			}

			if(controlID == ID_REDUCED_SIZE)
			{
				UpdateReducedSize();
			}
		}

		static TVarPCF4Gui *inst;

	private:
		//------------------------------
		// internal attributes
		void UpdateDistanceFactor()
		{
			float w = mDlg->GetSlider(ID_DISTANCE_FACTOR)->GetValue() / 100.0f;

			mModel->mDistanceFactor = 4.0f * w + 1.0f * (1.0f - w);

			WCHAR buf[64];
			StringCchPrintfW(buf, 64, L"Distance Factor %8.6f", mModel->mDistanceFactor);
			mDlg->GetStatic(ID_DISTANCE_FACTOR_TEXT)->SetText(buf);
		}

		void UpdateReducedSize()
		{
			mModel->mCurrReduceFactor = (int)mDlg->GetComboBox(ID_REDUCED_SIZE)->GetSelectedIndex();
			mModel->mShadowMapParamDuty = true;
		}
		
		//------------------------------
		// internal operations
	};

	TVarPCF4Gui *TVarPCF4Gui::inst = NULL;

	//--------------------------------------------------------------------------
	TVarPCF4::TVarPCF4()
	{
		mPendingShadowMapSize = 512;
		mReduceFactors.push_back(0.5f);
		mReduceFactors.push_back(0.25f);
		mReduceFactors.push_back(0.125f);

		mDilateCnts.push_back(1);
		mDilateCnts.push_back(2);
		mDilateCnts.push_back(3);

		mCurrReduceFactor = 1;
	}
	
	//--------------------------------------------------------------------------
	TVarPCF4::~TVarPCF4()
	{
	}

	//--------------------------------------------------------------------------
	void TVarPCF4::Init(TResourcePool *resPool, TGuiManager *guiMgr)
	{
		resPool->Add<IShader>("VarPCF4.Apply", make_tuple( STR("../media/shader/VarPCF4.Apply.fx"), STR("Apply") ) );
		resPool->Add<IShader>("VarPCF4.Generate", make_tuple( STR("../media/shader/VarPCF4.Generate.fx"), STR("Generate") ) );
		resPool->Add<IShader>("VarPCF4.DownSample", make_tuple( STR("../media/shader/VarPCF4.DownSample.fx"), STR("DownSample") ) );
		resPool->Add<IShader>("VarPCF4.Dilate", make_tuple( STR("../media/shader/VarPCF4.Dilate.fx"), STR("Dilate") ) );
	
		TVarPCF4Gui::inst = new TVarPCF4Gui(this, guiMgr);

		mShadowMapParamDuty = true;
	}

	//--------------------------------------------------------------------------
	void TVarPCF4::RenderScene(
		render::IRenderer *renderer,
		TResourcePool *resPool,
		TObjectManager *objMgr)
	{

		if(mShadowMapParamDuty)
		{
			RemoveShadowMaps(resPool);
			CreateShadowMaps(resPool);

			mShadowMapParamDuty = false;
		}

		resPool->Get<IShader>("VarPCF4.Apply")->BindParam(
			"VAR_PCF_DISTANCE_FACTOR",
			&mDistanceFactor,
			sizeof(float) );

		GenerateShadowMap(renderer, resPool, objMgr);

		RenderSceneWithShadowMap(renderer, resPool, objMgr);
	}
	
	//--------------------------------------------------------------------------
	void TVarPCF4::Final(TResourcePool *resPool)
	{
		delete TVarPCF4Gui::inst;

		resPool->Remove<IShader>("VarPCF4.Apply");
		resPool->Remove<IShader>("VarPCF4.Generate");
		resPool->Remove<IShader>("VarPCF4.DownSample");
		resPool->Remove<IShader>("VarPCF4.Dilate");

		RemoveShadowMaps(resPool);
	}

	//--------------------------------------------------------------------------
	void TVarPCF4::ChangeShadowMapSize(size_t sz)
	{
		mPendingShadowMapSize = (int)sz;
		mShadowMapParamDuty = true;
	}

	//--------------------------------------------------------------------------
	const char* TVarPCF4::GetDescription()
	{
		return "Dual Resolution Percentage Closer Filtering";
	}
	
	//--------------------------------------------------------------------------
	void TVarPCF4::CreateShadowMaps(TResourcePool *resPool)
	{
		float reduceFactor = mReduceFactors[mCurrReduceFactor];

		CreateShadowMap(resPool, "VarPCF4.SMap", IRenderedTexture::FLOAT16_GR, mPendingShadowMapSize);
		CreateShadowMap(resPool, "VarPCF4.SMap.1over4", IRenderedTexture::FLOAT16_GR, (size_t)(mPendingShadowMapSize * reduceFactor) );
		
		CreateShadowMap(resPool, "VarPCF4.SMap.temp", IRenderedTexture::FLOAT16_GR, (size_t)(mPendingShadowMapSize * 0.5f) );
		CreateShadowMap(resPool, "VarPCF4.SMap.occluder", IRenderedTexture::FLOAT16_GR, (size_t)(mPendingShadowMapSize * 0.5f) );

		float rsmSize = (float)mPendingShadowMapSize * reduceFactor;
		float omSize = (float)mPendingShadowMapSize * 0.5f;

		resPool->Get<IShader>("VarPCF4.Dilate")->BindParam(
			"INV_INPUT_SIZE",
			&TVec2(1.0f / omSize, 0.5f / omSize ),
			sizeof( TVec2) );


		resPool->Get<IShader>("VarPCF4.Apply")->BindParam(
			"INV_OCCLUDER_MAP_SIZE",
			&TVec2(1.0f / omSize, 0.5f / omSize ),
			sizeof( TVec2) );
		
		resPool->Get<IShader>("VarPCF4.Apply")->BindParam(
			"REDUCED_SHADOWMAP_SIZE",
			&rsmSize,
			sizeof(float) );

		resPool->Get<IShader>("VarPCF4.Apply")->BindParam(
			"INV_REDUCED_SHADOWMAP_SIZE",
			&TVec2(1.0f / rsmSize, 0.5f / rsmSize ),
			sizeof( TVec2) );

	}

	//--------------------------------------------------------------------------
	void TVarPCF4::RemoveShadowMaps(TResourcePool *resPool)
	{
		resPool->Remove<IRenderedTexture>("VarPCF4.SMap");
		resPool->Remove<IRenderedTexture>("VarPCF4.SMap.1over4");
		resPool->Remove<IRenderedTexture>("VarPCF4.SMap.temp");
		resPool->Remove<IRenderedTexture>("VarPCF4.SMap.occluder");
	}

	//--------------------------------------------------------------------------
	void TVarPCF4::CreateShadowMap(
		TResourcePool *resPool,
		const TStr &name,
		size_t format,
		size_t size)
	{
		resPool->Add<IRenderedTexture>(
			name, 
			make_tuple( format,
						size,
						size,
						true) );

		resPool->Get<IRenderedTexture>(name)->SetFilterMode(
			ITexture::POINT, ITexture::POINT, ITexture::POINT);

		resPool->Get<IRenderedTexture>(name)->SetAddressMode(
			ITexture::CLAMP, ITexture::CLAMP, ITexture::CLAMP);
	}

	//--------------------------------------------------------------------------
	void TVarPCF4::GenerateShadowMap(
		render::IRenderer *renderer,
		TResourcePool *resPool,
		TObjectManager *objMgr)
	{
		IShader *shader = resPool->Get<IShader>("VarPCF4.Generate");

		renderer->BindRenderTarget(resPool->Get<IRenderedTexture>("VarPCF4.SMap"), NULL, NULL, NULL, true);
		renderer->ClearColor( TVec4(1.0f, 1.0f, 1.0f, 1.0f) );
		renderer->ClearDepth(1.0f);

		objMgr->RenderCasterObject(renderer, shader);
		objMgr->RenderReceiverObject(renderer, shader);

		renderer->BindTexture(0, resPool->Get<IRenderedTexture>("VarPCF4.SMap") );
		renderer->BindRenderTarget(resPool->Get<IRenderedTexture>("VarPCF4.SMap.1over4"), NULL, NULL, NULL, true);
		renderer->RenderScreenQuad( resPool->Get<IShader>("VarPCF4.DownSample") );

		renderer->BindTexture(0, resPool->Get<IRenderedTexture>("VarPCF4.SMap") );
		renderer->BindRenderTarget(resPool->Get<IRenderedTexture>("VarPCF4.SMap.occluder"), NULL, NULL, NULL, true);
		renderer->RenderScreenQuad( resPool->Get<IShader>("VarPCF4.DownSample") );

		
		for(int i=0; i<mDilateCnts[mCurrReduceFactor]; ++i)
		{
			renderer->BindTexture(0, resPool->Get<IRenderedTexture>("VarPCF4.SMap.occluder") );
			renderer->BindRenderTarget(resPool->Get<IRenderedTexture>("VarPCF4.SMap.temp"), NULL, NULL, NULL, true);
			renderer->RenderScreenQuad( resPool->Get<IShader>("VarPCF4.Dilate") );

			renderer->BindTexture(0, resPool->Get<IRenderedTexture>("VarPCF4.SMap.temp") );
			renderer->BindRenderTarget(resPool->Get<IRenderedTexture>("VarPCF4.SMap.occluder"), NULL, NULL, NULL, true);
			renderer->RenderScreenQuad( resPool->Get<IShader>("VarPCF4.Dilate") );
		}

		renderer->BindTexture(0, NULL );
	}

	//--------------------------------------------------------------------------
	void TVarPCF4::RenderSceneWithShadowMap(
		render::IRenderer *renderer,
		TResourcePool *resPool,
		TObjectManager *objMgr)
	{
		IShader *shader = resPool->Get<IShader>("VarPCF4.Apply");

		renderer->RestoreDefaultRenderTarget();
		
		renderer->BindTexture(3, resPool->Get<IRenderedTexture>("VarPCF4.SMap") );
		renderer->BindTexture(4, resPool->Get<IRenderedTexture>("VarPCF4.SMap.1over4") );
		renderer->BindTexture(5, resPool->Get<IRenderedTexture>("VarPCF4.SMap.occluder") );

		objMgr->RenderCasterObject(renderer, shader);
		objMgr->RenderReceiverObject(renderer, shader);
		
		renderer->BindTexture(3, NULL);
		renderer->BindTexture(4, NULL);
		renderer->BindTexture(5, NULL);
	}

	//--------------------------------------------------------------------------
}
}
}
